#include "sock.h"
#include "error.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>

#if defined(_WIN32)
#include <winsock2.h>

// see ws2tcpip.h for AI_ADDRCONFIG
#if _WIN32_WINNT < 0x0600
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x0600
#endif

#include <ws2tcpip.h>
#else
#include <netdb.h>
#endif

void
sock_init()
{
#if defined(_WIN32)
    WSADATA data;
    int result = WSAStartup(MAKEWORD(2,2), &data);
    if (result != 0) {
        fprintf(stderr, "init win sock fail\n");
        exit(1);
    }
#endif
}

void
sock_cleanup()
{
#if defined(_WIN32)
    WSACleanup();
#endif
}

int
sock_open_clientfd(const char *hostname, const char *port)
{
    struct addrinfo hints;
    struct addrinfo *result;
    int code;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_ADDRCONFIG;
    hints.ai_flags |= AI_NUMERICSERV;

    if ((code = getaddrinfo(hostname, port, &hints, &result)) != 0)
        gai_error(code, "getaddrinfo fail");

    int clientfd = -1;
    struct addrinfo *p = result;

    char conn_host[100];
    char conn_port[10];
    int flag = NI_NUMERICHOST | NI_NUMERICSERV;
    
    for (; p; p = p->ai_next) {
        if ((clientfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
            continue;

        if ((code = getnameinfo(p->ai_addr, p->ai_addrlen, conn_host, sizeof(conn_host), conn_port, sizeof(conn_port), flag)) != 0)
            gai_error(code, "getnameinfo fail");
        fprintf(stdout, "try connecting to %s:%s\n", conn_host, conn_port);

        if (connect(clientfd, p->ai_addr, p->ai_addrlen) == 0)
            break;
        if (close(clientfd) != 0)
            unix_error("close fail");
    }

    freeaddrinfo(result);

    return !p ? -1 : clientfd;
}

int
sock_open_listenfd(const char *port)
{
    struct addrinfo hints;
    struct addrinfo *result;
    int code;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
    hints.ai_flags |= AI_NUMERICSERV;

    if ((code = getaddrinfo(NULL, port, &hints, &result)) != 0)
        gai_error(code, "getaddrinfo");

    int val = 1;
    int listenfd = -1;
    struct addrinfo *p = result;

    char bind_host[100];
    char bind_port[10];
    int flag = NI_NUMERICHOST | NI_NUMERICSERV;

    for (; p; p = p->ai_next) {
        if ((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
            continue;

        if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (const void*)&val, sizeof(val)) != 0)
            unix_error("setsockopt fail");

        if ((code = getnameinfo(p->ai_addr, p->ai_addrlen, bind_host, sizeof(bind_host), bind_port, sizeof(bind_port), flag)) != 0)
            gai_error(code, "getnameinfo fail");
        fprintf(stdout, "try binding to %s:%s\n", bind_host, bind_port);

        if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0)
            break;
        if (close(listenfd) != 0)
            unix_error("close fail");
    }

    freeaddrinfo(result);

    if (!p)
        return -1;

    if (listen(listenfd, 1024) != 0)
        unix_error("listen fail");

    return listenfd;
}

